//
// Created by syj on 2024/3/14.
//


#include "middle/log/logger.h"

#include <fstream>

#include "middle/log/common/async.h"
#include "middle/log/common/error_handle.h"
#include "middle/log/common/utils.h"
#include "middle/log/sinks/remote_sink.h"
//#include "middle/util/filesystem.h"
#include "nlohmann/json.hpp"
#include "spdlog/sinks/ringbuffer_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"

namespace log {

/** 未初始化日志后端日志缓存数量 */
    constexpr size_t kRingbufferSize = 1024;

/** 默认单日志文件大小 5M */
    constexpr size_t kDefaultMaxLogFileSize = 1024 * 1024 * 5;

/** 日志异步线程缓存条目 */
    constexpr size_t kAsyncQueueSize = 8192;

/** 日志异步线程个数 */
    constexpr size_t kThreadCount = 1;

/** 日志异步线程名称 */
    constexpr char const *const kThreadName = "mos_log";

/** 日志配置文件名 */
    static constexpr char const *const kConfigFileName = "log.json";

/** 日志默认配置 */
    static LogConfig config_{.id = "mv_app",
            .level = LogLevel::info,
            .mode = LogMode::kConsole,
            .filedir = "./",
            .filename = "mv_app.log",
            .filesize = kDefaultMaxLogFileSize,
            .filenum = 3,
            .interval = 3};

/** 【年-月-日 时:分:秒.毫秒】【日志等级】【上下文】 日志内容*/
    const char *const kPattern = "[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%n] %v";

/** 日志输出控制标志 */
    std::atomic_bool is_stop_{false};

/** 日志初始化标志 */
    std::atomic_bool initialized_{false};

/** 日志初始化完成标志 */
    std::atomic_bool is_read_{false};

    static std::vector<spdlog::sink_ptr> &GetSinks() {
        static std::vector<spdlog::sink_ptr> sinks_;
        return sinks_;
    }

    static std::mutex &GetSinksMutex() {
        static std::mutex sinks_mutex_;
        return sinks_mutex_;
    }

/**
* @brief 输出ringbuffer中的日志到新的日志后端.
*
* @param ringbuffersink ringbuffer日志后端
* @param outputsinks 需要输出的日志后端
*/
    void OutputRingbuffer(spdlog::sink_ptr &inputsink,
                          std::vector<spdlog::sink_ptr> const &outputsinks) {
        auto ringbuffersink =
                std::dynamic_pointer_cast<spdlog::sinks::ringbuffer_sink_mt>(inputsink);
        if (!ringbuffersink) return;
        for (auto &buf : ringbuffersink->last_raw()) {
            for (auto &sink : outputsinks) {
                sink->log(buf);
            }
        }
    }

/**
* @brief 初始化日志系统.
*/
    void Init() {
        /** 设置日志系统 */
        spdlog::set_level(spdlog::level::trace);
        spdlog::set_pattern(kPattern);
        spdlog::flush_every(std::chrono::seconds(config_.interval));
        init_thread_pool(kAsyncQueueSize, kThreadCount,
                         []() { pthread_setname_np(pthread_self(), kThreadName); });

        /** 设置ringbuffer保存日志 */
        if (!initialized_) {
            GetSinks().push_back(
                    std::make_shared<spdlog::sinks::ringbuffer_sink_mt>(kRingbufferSize));
        }

        /** 异常退出时将缓冲输出终端 */
        std::set_terminate([] {
            Logger logger{"MOS"};
            if (std::current_exception()) {
                try {
                    std::rethrow_exception(std::current_exception());
                } catch (std::exception const &e) {
                    logger->critical("Uncaught exception: {}", e.what());
                } catch (...) {
                    logger->critical("Uncaught exception: unknown");
                }
            }
            logger->critical("Execution terminated");
            if (!initialized_) {
                auto sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
                std::vector<spdlog::sink_ptr> new_sinks{sink};
                OutputRingbuffer(GetSinks()[0], new_sinks);
            }
            spdlog::apply_all([&](auto logger) { logger->flush(); });
            std::abort();
        });
    }

/**
* @brief 获取日志器
*
* @param name 日志器名称
* @return std::shared_ptr<spdlog::logger>
*/
    std::shared_ptr<spdlog::logger> GetLogger(std::string const &name) {
        std::lock_guard<std::mutex> lock(GetSinksMutex());

        static std::once_flag flag;
        std::call_once(flag, [&]() { Init(); });

        auto logctx = name.empty() ? log::GetProcessName() : name;
        auto it = spdlog::get(logctx);
        if (it) return it;

        return create_async_nb(logctx, GetSinks().begin(), GetSinks().end());
    }

/**
* @brief 创建日志后端.
*
* @param config 日志配置
*/
    std::vector<spdlog::sink_ptr> MakeSinks(LogConfig const &config) {
        std::vector<spdlog::sink_ptr> sinks;
        if ((config.mode & LogMode::kConsole) == LogMode::kConsole) {
            sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
        }

        if ((config.mode & LogMode::kFile) == LogMode::kFile)
        {
//            mos::util::path log_path(config.filedir);
//            log_path = log_path / config.filename;
            sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
                    std::string(config.filedir + "/" + config.filename),
                    config.filesize,
                    config.filenum,
                    true));
        }

        if ((config.mode & LogMode::kRemote) == LogMode::kRemote) {
            std::promise<bool> promise;
            auto future = promise.get_future();
            auto sink = std::make_shared<remote_sink_mt>(config, promise);
            if (future.get() == true) {
                sinks.push_back(sink);
            } else {
                Logger("mos")->error("Failed to create remote sink");
            }
        }
        return sinks;
    }

/**
* @brief 替换日志后端.
*
* @param old_sinks 旧的日志后端
* @param new_sinks 新的日志后端
*/
    void DoInitLogging(std::vector<spdlog::sink_ptr> &old_sinks,
                       std::vector<spdlog::sink_ptr> const &new_sinks) {
        spdlog::set_level(config_.level);

        std::unique_lock<std::mutex> lock(GetSinksMutex());
        OutputRingbuffer(GetSinks()[0], new_sinks);
        old_sinks = new_sinks;
        lock.unlock();

        spdlog::apply_all([&](auto logger) { logger->sinks() = old_sinks; });
    }

    void InitAppLogging(LogLevel level) {
        if (initialized_.load()) return;
        initialized_.store(true);

        config_.level = level;

        DoInitLogging(GetSinks(), MakeSinks(config_));
    }

    void InitAppLogging(const std::string &config_file_path) {
        if (initialized_.load()) return;
        initialized_.store(true);

//        mos::util::path config_path(config_file_path);
//        config_path = config_path / kConfigFileName;
//        mos::log::ParseConfig(config_path.string(), config_);

        DoInitLogging(GetSinks(), MakeSinks(config_));
    }

    void InitAppLogging(std::string id, LogMode mode, LogLevel level,
                        const std::string dirpath, const std::string filename,
                        std::size_t filesize, std::size_t filenum) {
        if (initialized_.load()) return;
        initialized_.store(true);

        config_.id = id;
        config_.level = level;
        config_.mode = mode;
        if (!dirpath.empty()) config_.filedir = dirpath;
        if (!filename.empty()) {
            config_.filename = filename;
        } else {
            config_.filename = config_.id + ".log";
        }
        if (filesize > 0) config_.filesize = filesize;
        if (filenum > 0) config_.filenum = filenum;

        DoInitLogging(GetSinks(), MakeSinks(config_));
    }

    void SetLevel(LogLevel level, std::optional<std::string> const &context) {
        if (context.has_value()) {
            spdlog::get(context.value())->set_level(level);
        } else {
            spdlog::set_level(level);
        }
    }

    LogLevel GetLevel(std::optional<std::string> const &context) {
        return context.has_value() ? spdlog::get(context.value())->level()
                                   : spdlog::get_level();
    }

    std::string GetLevelStr(std::optional<std::string> const &context) {
        auto level_string_views = spdlog::level::to_string_view(GetLevel(context));
        return std::string(level_string_views.data(), level_string_views.size());
    }

    void Stop() {
        if (is_stop_.load()) return;
        is_stop_.store(true);

        spdlog::log(spdlog::level::info, "Stop logging");
        spdlog::enable_backtrace(kRingbufferSize);
        spdlog::apply_all([&](auto logger) { logger->flush(); });
    }

    void Resume(bool is_replay_backtrace) {
        if (!is_stop_.load()) return;
        is_stop_.store(false);

        if (is_replay_backtrace) {
            spdlog::dump_backtrace();
        }
        spdlog::disable_backtrace();
        spdlog::log(spdlog::level::info, "Resume logging");
    }

    Logger::Logger(std::string const &name) : logger_(GetLogger(name)) {}

    spdlog::logger *Logger::operator*() const { return get(); }
    spdlog::logger *Logger::operator->() const { return get(); }
    spdlog::logger *Logger::get() const { return logger_.get(); }

    LogStream Logger::LogCritical() noexcept {
        return LogStream(logger_, LogLevel::critical);
    }
    LogStream Logger::LogFatal() noexcept {
        return LogStream(logger_, LogLevel::critical);
    }
    LogStream Logger::LogError() noexcept {
        return LogStream(logger_, LogLevel::err);
    }
    LogStream Logger::LogWarn() noexcept {
        return LogStream(logger_, LogLevel::warn);
    }
    LogStream Logger::LogInfo() noexcept {
        return LogStream(logger_, LogLevel::info);
    }
    LogStream Logger::LogDebug() noexcept {
        return LogStream(logger_, LogLevel::debug);
    }
    LogStream Logger::LogVerbose() noexcept {
        return LogStream(logger_, LogLevel::trace);
    }

    void PrintErrors(std::exception const &errors) {
        return PrintErrors(Logger("mos"), errors);
    }

    void PrintErrors(std::vector<std::string> const &errors) {
        return PrintErrors(Logger("mos"), errors);
    }

    void PrintErrors(Logger const &logger, std::vector<std::string> const &errors) {
        if (errors.empty()) return;

        logger->error("{}", errors[0]);
        for (auto i = 1u; i < errors.size(); ++i) {
            logger->error("  caused by: {}", errors[i]);
        }
    }

    void PrintErrors(Logger const &logger, std::exception const &errors) {
        PrintErrors(logger, dump_errors(errors));
    }

}  // namespace log
